有人说:“C 语言是 ‘面向过程’ 的,不适合面向对象。”
有人说:“如果你想在 C 里做 OOP,就用 Objective-C。”
还有人说:“那就直接在 C++ 里面写 C 代码呗。”
但这些都是 “逃避”。真正的 C 程序员 “不逃避”。
我们要在 “纯 C” 里实现面向对象——不是“模拟”,不是“伪装”,
而是 “基于类的底层实现”。我们要用 struct、函数指针、
void* 和 malloc 来 “硬造” 出
封装、继承、多态、抽象 这四大支柱。
今天,我们就来学习这门 “C 的 OOP 邪典”。准备好了吗?让我们 “用 C 写 C++”——但比 C++ 更 “硬核”。
struct 当“类”,用函数指针当“方法”
在 C++ 里,类有 “成员变量” 和 “成员函数”。
在 C 里,我们用 struct 放 “成员变量”,用 “函数指针”
放 “成员函数”。这叫 “手动绑定”。
// 用 struct 定义一个“类”
typedef struct {
// 成员变量
int x;
int y;
// 成员函数(函数指针)
void (*setX)(void*, int);
int (*getX)(void*);
void (*print)(void*);
} Point;
// 函数实现(第一个参数是 this 指针)
void point_setX(void* self, int v) {
((Point*)self)->x = v;
}
int point_getX(void* self) {
return ((Point*)self)->x;
}
void point_print(void* self) {
Point* p = (Point*)self;
printf("(%d, %d)\n", p->x, p->y);
}
// “构造函数”
Point* new_Point(int x, int y) {
Point* p = (Point*)>malloc(sizeof(Point));
p->x = x;
p->y = y;
p->setX = point_setX;
p->getX = point_getX;
p->print = point_print;
return p;
}
// 这就是 C 的“类”——没有语法糖,全是“手工活”
struct 嵌套 模拟“父类”C++ 的继承是“is-a”关系。在 C 里,我们用一个 struct “包含” 另一个 struct——让“父类”成为“子类”的 “第一个成员”。 这样,子类的指针就可以 “直接” 当父类的指针用。
// “父类”
typedef struct {
int x;
int y;
void (*print)(void*);
} Shape;
// “子类”——把 Shape 放在第一个位置
typedef struct {
Shape parent; // 继承 Shape
int radius; // 子类自己的成员
} Circle;
// 子类的“构造函数”
Circle* new_Circle(int x, int y, int r) {
Circle* c = (Circle*)malloc(sizeof(Circle));
c->parent.x = x;
c->parent.y = y;
c->parent.print = circle_print; // 重写父类方法
c->radius = r;
return c;
}
// 现在你可以把 Circle* 当作 Shape* 用
Shape* s = (Shape*)c; // 向上转型
s->print(s); // 调用的是 circle_print
// 这就是 C 的“继承”——“第一个成员”继承法
vtable 实现“动态派发”C++ 的多态靠 “虚表”(vtable)。C 里没有,但我们可以 “手动” 造一个。用一个 “函数指针表” 来存储所有虚函数,然后每个对象 “指向” 这个表。这叫 “手动虚表”——一种 “体力活”。
// 接上文
// 虚表(vtable)
typedef struct {
void (*print)(void*);
void (*move)(void*, int, int);
} ShapeVTable;
// 基类:包含虚表指针
typedef struct {
ShapeVTable* vtable;
int x, y;
} Shape;
// 子类:继承基类
typedef struct {
Shape parent;
int radius;
} Circle;
// 虚表实例(每个类只有一个)
ShapeVTable shape_vtable = { shape_print, shape_move };
ShapeVTable circle_vtable = { circle_print, circle_move };
// 现在调用时,通过 vtable 间接调用
s->vtable->print(s); // 多态!
// 这就是 C 的“多态”——比 C++ 的虚表更“透明”
不完整的 struct 实现“接口”
C++ 的抽象类 “不能被实例化”。在 C 里,我们可以在 .h 文件里
“声明” 一个 struct,但不 “定义” 它。这样,外部代码
只能通过 “指针” 操作它,不能直接分配它。这叫 “不透明指针”
(Opaque Pointer)——一种 “手动抽象”。
// shape.h —— 抽象基类
typedef struct Shape Shape; // 只声明,不定义
// 只能通过函数操作 Shape
void shape_print(Shape* s);
void shape_move(Shape* s, int dx, int dy);
Shape* shape_create(int x, int y);
// shape.c —— 具体定义(用户看不到)
struct Shape {
int x, y;
void (*print)(Shape*);
};
// 用户不知道 Shape 的内部结构——只能通过接口操作
// 这就是 C 的“抽象”——一种“信息隐藏”的暴力美学
// ============ shape.h ============
#ifndef SHAPE_H
#define SHAPE_H
typedef struct Shape Shape;
Shape* shape_new(int x, int y);
void shape_delete(Shape* s);
void shape_print(Shape* s);
void shape_move(Shape* s, int dx, int dy);
#endif
// ============ shape.c ============
#include "shape.h"
#include <stdio.h>
#include <stdlib.h>
struct Shape {
int x, y;
void (*print)(Shape*);
};
static void shape_print_impl(Shape* s) {
printf("Shape at (%d, %d)\n", s->x, s->y);
}
Shape* shape_new(int x, int y) {
Shape* s = malloc(sizeof(Shape));
s->x = x; s->y = y;
s->print = shape_print_impl;
return s;
}
void shape_print(Shape* s) { s->print(s); }
void shape_move(Shape* s, int dx, int dy) { s->x += dx; s->y += dy; }
void shape_delete(Shape* s) { free(s); }
// ============ main.c ============
#include "shape.h"
int main() {
Shape* s = shape_new(10, 20);
shape_print(s);
shape_move(s, 5, -3);
shape_print(s);
shape_delete(s);
return 0;
}
// 这就是 C 的 OOP——没有语法糖,全是“手工活”
// 但它“确实”是面向对象的
如果你真的想在 C 里用 OOP,“请考虑一下你的维护成本”。
手动虚表、手动继承、手动封装——这些都 “可以工作”,但 “不值得”。
如果你需要 OOP,“请用 C++”。它不是“花哨”,它是 “工具”。
如果你坚持用 C,“请接受它的过程式本质”——它不是“落后”,它是 “简洁”。
—— 一个曾经在 C 里实现 OOP,然后发现 C++ 更省事的人